home *** CD-ROM | disk | FTP | other *** search
/ Mac Mania 2 / MacMania 2.toast / Demo's / Tools&Utilities / Programming / MSTPChart / Article.txt < prev    next >
Encoding:
Text File  |  1993-05-26  |  29.9 KB  |  251 lines  |  [TEXT/MSWD]

  1. Quickstart Macintosh Programming
  2. By Dr. Craig Stone, San Jose State University
  3. From the SciTechJournal, May, 1993
  4. The Official Journal of the Macintosh Scientific and Technical Users Association
  5.  
  6.  
  7. Editor’s Introduction
  8.     This is the second in our series on Macintosh programming for scientists and engineers. We intend in this series to describe the range of programming options available, from HyperCard and other ultra-high-level environments to prototypers, canned libraries, XCMDs and extensions, visual programming tools, and OOP frameworks.
  9.     With this article we present a simple, working THINK C program which displays a periodic table of the elements. Clicking on an element displays information about that element in a separate window. The source code, THINK C project, and resource files are available on AOL in the MacSciTech area or our Internet site as MSTPChart.sea, or on disk from MacSciTech for $5 (members), $10 (non-members).
  10.     The purpose of this article is twofold. First, for those who are considering Macintosh programming in a high-level language, it presents in the most compact (yet understandable) form we have seen, the essentials of how a Macintosh application differs from the Hit Any Key to Continue programming we are all familiar with. The source code structure, clarity, and naming conventions make it an exceptional learning laboratory. Second, for those interested in something at a higher level (HyperCard et al), a brief exposure to the guts of a Macintosh application can help you appreciate just how much work the higher level environments do for you, and therefore help you better select the right environment for you.
  11.     If you want to see this article expanded into a series in its own right, either in the Journal or by subscription, please contact MacSciTech (see p. 4). [To contact MacSciTech: 49 Midgley Lane, Worcester, MA 01604; voice 508-755-5242; fax 508-795-1636; Applelink: MacSciTech; AOL: SciTechMac; Internet: SciTechMac@aol.com or scitech@ra.nrl.navy.mil.]
  12.  
  13.  
  14.  
  15. We scientists and engineers have grown up used to the idea of creating small programs to perform a quick data transformation or solve some sort of problem. The programs were crude Hit Any Key to Continue (“HAKTC”) programs, but this was sufficient. In hindsight, it was comforting that our programs were similar in character to the sophisticated commercial products: we all had to Hit Any Key to Continue.
  16.     Times have changed. We now launch applications, create and interact with windows and menus, cut, copy and paste information. Whereas programs put the burden on the user, applications put the burden on the programmer. That means we scientists and engineers either continue to write programs or address the daunting task of creating applications.
  17.     Not that programming in a windowing environment is difficult. It is actually a set of very trivial steps. The incredible number of trivial steps is unfortunately overwhelming to the novice. For several years I could not justify spending the apparently large amount of time to cross the barrier from programs to applications. Much of the barrier was merely perceived, but a significant amount was simply that I had no model relevant to science that I could follow, easing myself into the real world.
  18.     My goal with this article is to present the Macintosh toolbox in as simple a manner as possible. I will not go into detail about event loops, handling various system versions, or any other topic that distracts from this goal. Instead I will focus on quickly producing a working model and showing how that model can be modified for another application. I cannot emphasize enough that the fastest way to learn what is going on is to load the THINK C project for this application and run it, tracing the program flow with the debugger.
  19.     A philosophy which drives the format of this article is—steal from yourself. Create new resources like dialogs and menus by modifying existing ones with ResEdit. The source code for the event loop, the dialogs, and the window and menu control functions were stolen from another application I have written. Use the code for this application as a template to build you own applications.
  20.     The example application produces a working menu bar, allows us to display an about box, select any items listed under the Apple menu, and open a window displaying a color periodic chart under a custom menu we call Chart. Within the periodic chart window we will detect mouse events, and if that event is a double click a new window appears whose title is the name of the element. Names of the elements and their symbols are stored as resources and called at various places in the application.
  21.     No, the application does not do very much! Our goal is to create a framework within which we can learn about Macintosh programming. Later we can add to the application, expand its capabilities, and dive deeper into the toolbox functions. This article focuses on creating windows and interacting with windows in a simple manner. (For clarity, Toolbox variables, types, and functions appear in boldface.)
  22.  
  23. A Word About Events
  24.     Perhaps the biggest difference between Macintosh programs and HAKTC programs is the event loop. A typical HAKTC program reaches a line which requests input from the keyboard, and waits there until the user cooperates. In contrast, the Macintosh maintains a first-in-first-out event queue. Events can be generated by the mouse and keyboard as well as by the operating system itself. Each event record in the queue contains information about the event (when did it happen? where was the mouse clicked? what key was pressed?) which is available to the program.
  25.     The core of any Macintosh program is the event loop. The program will spend most of its time there, waiting for the user to do something. The loop will repeat forever unless the user exits it by quitting the program. When the program detects an event in the queue, it reads the event record, removes the event from the queue, and then calls whatever routines are appropriate to respond to the event. The user is no longer needlessly restricted to providing the program with the input it wants, from the device it wants, when it wants it.
  26.  
  27. Window Management
  28.     There are four basic phases in window management: Open, Close, Update, and Do. We will have at least four routines to support these operations. Prototypes for these routines for both the periodic chart and elements dialogs are given in Listing 1 along with global variables that are needed to support windows. Generally only the Do_… functions require a variable be passed to them. In this case it is a pointer to or the address of an EventRecord.
  29.     Windows in our application are actually dialogs that we will treat as windows. The primary difference between windows and dialogs is that we can easily use ResEdit to attach controls (buttons, scroll bars,…) to the dialogs. These dialogs are treated as windows in the sense that we go back to the main event loop when we have finished processing an event.
  30.     Two types of global variables are shown in Listing 1, a DialogPtr and a Point. PeriodicChartDlog and ElementsDlog are pointers to the dialog information records used throughout the application. They are initially set to NIL in the application and reset to NIL when the dialogs are closed. A pointer is a 4-byte variable which contains the address of the thing pointed to—here, a Macintosh data structure called a dialog information record. This may not have occurred to you as a HAKTC programmer, but everything you see on the screen has to be represented somewhere in memory, or it doesn’t exist! So we will ask the Macintosh to create a new dialog for us, according to our specifications, and the Mac will respond by allocating memory for the dialog record, filling it with appropriate data, and handing us back a pointer to that memory. We can use knowledge of the dialog record structure to do things in our program, but we don’t have to. All we need is that pointer.
  31.     When a mouse down event is detected in the Periodic Chart window our Do_PeriodicChart routine will store the local coordinates of the mouse click in our variable PeriodicChartWhere. (Local coordinates are relative to the top left corner of a window; global coordinates are relative to the screen.) As our application grows in complexity, the Do_…routines will call an increasingly complex set of functions. It is often cleaner to create a global variable containing the mouse click location than to pass the Point as a parameter. However, you should be aware that the trend in programming philosophy, as exemplified by Object Oriented Programming, is to insulate the pieces of a program from one another and modularize them. Parameter passing can make your code more modular.
  32.     The ResID_… define the resource ID numbers for our dialogs. A resource ID number is used by the GetNewDialog toolbox function to access a dialog resource available to the program either in its resource fork or in the system file. Run ResEdit on the resource file included with the source code for our Periodic Table program. Double click on the DLOG resource and you will see the resource ID for the Periodic Table dialog is 201. Naming constants like this is good programming practice. It lets you change its value throughout the program in one stroke and eliminates the risk of missing an occurrence.
  33.     A pattern should be appearing in the variable definitions, the precompiler defines, and the function names. We are using names with standard prefixes and suffixes. This makes the code clear and easily reusable. For example, I created the elements dialog source code by opening Periodic.c saving it as Elements.c and changing every occurrence of “PeriodicChart” to “Elements”. Appropriate variables and defines were duplicated in the file MST_… and similarly renamed.
  34.  
  35. Open_Elements
  36.     We will use the Open_Elements function to create the element window and initialize any variables needed by the window management routines. (Remember, the element window and periodic table window are actually dialogs, a specialized form of window.) Our periodic chart function requires no initialization but, when we open the elements window, we want to set the window title to the current element. There may be some context that this open function is called after the window has been created. Test for a non-NIL value stored in the dialog pointer (ElementsDlog). If ElementsDlog is set, simply select the window with SelectWindow (bring it forward and make it active) and then return.
  37.     We must perform three operations in order to produce a visible dialog. First is the call to GetNewDialog. The resource ID number of the dialog resource is passed along with some default values of NIL, and -1 (type coerced into a WindowPtr). Calling the function with these default values lets the dialog manager routines create the dialog record structure in memory and causes the dialog to become the front-most window. Calling the functions ShowWindow and SelectWindow make the dialog visible and make it the active window.
  38.     Our elements window contains as the title the name of the element. Elemental names are stored in an STR# resource. We can access the names by calling the function GetIndString and passing to it the resource ID number of the string list (as defined by the constant ChemNamesStr, which is 1), and the atomic number passed to Open_Elements in thisElement. The element name is returned in the string variable thisString, which we also passed as a parameter to GetIndString. One important point to note is that our string variable has a type of Str255, a string that is 255 characters long. Many of the toolbox string functions require these Pascal strings and we may be constantly converting between the C and Pascal formats using the functions c2pstr and p2cstr.
  39.     The GetIndString function retrieves the name of our element from the string resource if our atomic number is less than 111. If the atomic number is 111 or larger we title the window “Element ###” where ### is the atomic number of the element. We initially store the C string “Element ” in thisString, append the character sequence that represents the atomic number, and convert the resulting string to a Pascal-formatted string. NumToString converts the atomic number into a Pascal-formatted string. We convert this string to a C format with the function p2cstr and perform the concatenation with the function strcat. MPW programmers should note that the numtostring function produces a string with a C format; this is different than the NumToString toolbox function which produces a string with a Pascal format. Finally, the window title is set by calling SetWTitle and passing to it the DialogPtr and the address of the Pascal string.
  40.  
  41. Getting and Setting Ports
  42.     A second great Macintosh departure from HAKTC programming is the concept of graphics ports. A port is, abstractly, just a data structure (called a GrafPort), with fields which contain all the information a Quickdraw command would need to know how to operate. That is, if you want to draw a blue line a distance of 5 pixels down from the current pen location which is one pixel wide with a solid pattern, you don’t pass loads of parameters like: Line(0,5, 1, solid, blue). Instead, you set the port’s pen color to blue, set the port’s pen size to one pixel wide, and set the port’s pen pattern to solid. Then you draw with Line(0,5).
  43.     Every window/dialog has one and only one graphics port. There are a variety of graphics ports that we can draw to and access in the multi-window Macintosh environment. Some may be windows or dialogs, other can even be off-screen graphics ports. Only one port can be the active port at a time, and many Quickdraw routines operate only on the currently active port (BeginUpdate, EndUpdate, RGBForeColor, RGBBackColor, MoveTo, Move, Line, TextFont, TextSize, FrameRect, and DrawString in Listing 4 all operate only on whatever the current port is set to). To improperly handle the graphics ports may cause strange drawing effects to occur. The general philosophy is to get the current port, save the pointer to the current port in a variable, set the port to the one we want to draw to, draw, and then restore the port to the original.
  44.     We use the new System 7.0 commands GetGWorld and SetGWorld (for Get and Set Graphics World) in our example application, but they are analogous to the GetPort and SetPort commands in the earlier systems. You can see this in action in Listing 4, where we save the current graphics port, make Periodic- ChartDlog the current port, redraw our periodic chart, and then restore the current port to the saved one when we are finished.
  45.  
  46. Close_Elements
  47.     The actions we need to take when the user closes our elements window are simple but crucial. Remember, nothing exists unless it exists somewhere in memory. We could just hide the window we want to close (say, Helium). Then, when we want a new elements window, call GetNewDialog again, place the result in ElementsDlog, and show the new window. However, the Macintosh will still have the memory for the Helium window reserved, taking up valuable space. Worse, we have now lost the reference to it, so we cannot reclaim the memory!
  48.     It is up to you, the programmer, to clean up after yourself. So we tell the Macintosh memory manager to release the memory by calling DisposDialog. It is good programming practice to make sure a pointer is not NIL before passing it to a toolbox call, even if you think your program flow cannot possibly produce such a result. Operating on NIL pointers produces system crashes. Finally, using our window management scheme, we restore the dialog pointer to a value of NIL.
  49.     If you think about it, why should merely destroying the dialog’s memory allocation remove its image from the screen? That answer is: it doesn’t! Fortunately, DisposDialog does a lot more for us than just release the dialog’s memory. Dispos-Dialog also removes the dialog’s image from the screen and its place in the Macintosh’s window list, and generates the required update and activate events (see the UpDate_PeriodicChart routine later on). All you have to do is call DisposDialog, and things looks right on the screen automatically.
  50.  
  51. UpDate_PeriodicChart
  52.     We perform all window drawing in the UpDate_… functions. This is actually not required but it is a tidy format to follow, especially in the beginning stages of Macintosh programming. A window needs updating whenever:
  53.     1.    an overlaid window has moved, uncovering a portion of the window;
  54.     2.    an overlaid window has been dismissed;
  55.     3.    the window has just become the active and front-most window; or
  56.     4.    a portion of the window’s contents has changed and needs to be redisplayed.
  57.     This brings me to a third really cool difference between HAKTC programming and Macintosh programming: update events. The system itself does all the hard work for you, keeping track of what pieces of what windows need updating (called the update region). It tells you about the first three situations above by generating an update event. In response, you call your Update_... routine. By beginning the routine with a call to BeginUpdate, the system automatically restricts all your drawing to just that portion of the window that needs updating. This speeds things up and reduces flickering of the display. The system takes care of redrawing the window frame. 
  58.     The fourth situation above does not arise in our program. It is usually handled in a very simple way which takes advantage of all the code we have written for the first three situations. We can add a portion of the window to the update region ourselves by calling InvalRect. Then just sit back and let everything happen. The system will generate an update event, and your program will automatically redraw the portion of the window you specified in your call to InvalRect.
  59.     Our periodic chart window contains two features, the periodic chart and a dotted frame around the window. I draw this dotted frame here to illustrate how to access information from the dialog record. We start the update routine by saving the current graphics port and setting the current port to that of the periodic chart dialog. Then we call BeginUpdate, which must always be balanced by a call to EndUpdate near the end of the routine.
  60.     The dotted line that frames the periodic chart is inset 13 pixels from the edge of the window. There are a lot a ways we could have drawn it. We could have set the pattern of our Quickdraw pen to a dotted one. Our program instead manually draws a pixel, skips a pixel, draws a pixel, etc. by incrementing a loop counter. The loop starts at 13 and ends 13 pixels from the right (or bottom) of the window. We know where the bottom and right of the window are by looking at PeriodicChartDlog->portRect.right and PeriodicChartDlog->portRect. bottom. The top and left side of a port are always 0 (local coordinates). We want a light blue border, so we set the forecolor to a value of Color_LtBlue, a global variable we defined outside the bounds of these functions. Whenever you introduce color to your application make sure you adopt a consistent policy of setting the forecolor to black and the backcolor to white when you are done. Interesting results are produced when you transfer off-screen pixel maps to a window with these colors set somewhere else. You will also save considerable time searching for the errant color change that could become noticeable as the application increases. (We speak from experience here!) Follow a similar policy when using fonts. The font is being set to 9 point geneva in our routine and then reset to 12 point system font (Chicago) at the end of the function.
  61.     The periodic chart is drawn using a simple for loop that draws a blue-framed rectangle and centered inside the rectangle draws the elemental symbol.Tthere are other ways we could have done this. For instance, we could have drawn a periodic table in MacDraw and saved it as a PICT resource, which we could display with a single call to DrawPicture.
  62.     We call RGBForeColor, again using our global variable Color_Blue. Coordinates for the rectangles were calculated and stored in the array PeriodicRects when the application was launched. To draw the chart we simply call FrameRect, passing it the address of the appropriate rectangle. Note that our for loop starts at 1, not 0. We have intentionally ignored the first element of the array to simplify the presentation of the code. (Actually, we in nuclear science choose to reserve it for the neutron!) Centering the elemental symbol requires us to move to the center of the rectangle (an average of the left and right coordinates), and then move left by half of the string length and down a bit. StringWidth allows us to determine the length in pixels of the character string. The symbol is printed using the command DrawString. In other applications you may need to display a C string. Convert it to a Pascal string before calling DrawString!
  63.  
  64. Do_Per-iodicChart
  65.     Our Do_ function has only two purposes: to detect a double click in one of our rectangles and to allow users to close the window through the escape key. I chose a double click to permit the elements to be selected via single click should you wish to add functionality to this program later on. I chose not to use Command-W to close since at this point our file menu, usually associated with Command-W, is dimmed. We begin the routine by copying the location of the mouseclick (if any) from thisEvent->where into PeriodicChartWhere, our global variable. Event coordinates are global, in the coordinates of the screen. Convert the global coordinates to local ones using GlobalToLocal.
  66.     “thisEvent->what” indicates what type of event occurred. If the event is a mouseDown event we want to compare the coordinates of the event with the array of rectangles. This comparison is made using a for loop and testing for a value of TRUE returned from the PtInRect function. A TRUE value is returned when the mouse click occurred within the bounds of the rectangle. Following a valid click one of the rectangles we call our custom function WaitDoubleClick, which we won’t describe here. WaitDoubleClick returns a value of Click_Double (defined as 2) if it is a valid double click. When this occurs we pass the atomic number, stored in loop1, to Open_Elements, described above.
  67.     If a keyboard event occurs we also check to see if the command key was pressed. Command, option, and shift key information is stored in the modifiers field of the event record structure. Bitwise And the value stored in modifiers with the mask cmdKey to determine if the command key was pressed. The result is 0 if it was not pressed and not zero if pressed. We only accept a value of 0 in this example. Another version of the bitwise manipulation is given inside the else if statement. We are converting the message field into key and character values. The variable testKey contains the ASCII value of the key, such as a 48 for a ‘0’. keyCode contains the Apple-assigned number for the key on the keyboard. Escape has a value of 53. If the escape key is pressed we call Close_PeriodicChart to dispose of the window.
  68.  
  69. Out Of Room!
  70.     I have no room to discuss details of the event loop, menu handling, and quitting. But if you have read this far, I bet that you will have no trouble understanding the source code for those areas. Especially if you step through it with THINK C’s debugger! MST
  71.  
  72.  
  73.  
  74. MacSciTech is considering expanding this article into a series in its own right, either in the Journal or by subscription, which would add functionality to the Periodic Chart program as we explore the Macintosh Toolbox. If you want to see such a series, you must let the MacSciTech office know, by letter, phone, fax, or e-mail! (See page 4.) 
  75.     
  76.  
  77.  
  78. Credits:
  79. Craig Stone is a professor of Nuclear Chemistry at San Jose State University in California and a founder and former director of MacSciTech. He can be reached at Stone.C on Applelink.
  80.  
  81.  
  82. Sidebar:  Macintosh System Design Philosophy
  83.     A pervasive feature of Macintosh programming has been variously called indirection, layering, and modularity. Note in the accompanying article, Dr. Stone “set the port’s pen width to one pixel wide.” The normal “programmer’s” approach would be to place the integer “1” into the pnSize field of the GrafPort, so: MyGrafPtr->pnSize=1, or even to write it there directly as an offset to MyGrafPtr. The problem with this approach is that the operating system cannot change and grow much without breaking your application. Suppose your application writes directly to an offset, and the next version of the system adds a field to GrafPort structures before the pnSize field? Suppose the next version keeps the pen size information somewhere other than the GrafPort structure?
  84.     The Macintosh solution is to provide system function calls to access almost all important system variables and data structures. The GrafPort structure could change radically with a new system version, but so long as your program calls SetPenState to change the pen size, the system software changes will be transparent to your program. This Macintosh philosophy of interposing consistent interfaces between the user and the application, between the application and the system, between the software and the hardware, and between this hardware and that hardware, is responsible for the intuitive, plug & play nature of our machines. The lack of this philosophy is why users of Microsoft/PC based systems report having a considerably more difficult time configuring individual machines and their peripherals as well as networks. – Mark Worthington
  85.  
  86.  
  87. Sidebar:  How Was I Supposed To Know That?
  88.     While each step in the Periodic Chart program’s process may make sense to you, you may nonetheless feel frustrated overall. For example, when you reached the line near the end of Listing 2 which says ShowWindow, you probably understood immediately that the command was necessary to make the window visible. But how in the world were you supposed to know that a new window isn’t visible in the first place?
  89.     You weren’t. You need two things. First, you need a reference like Inside Macintosh to tell you what is available to you in the Macintosh Toolbox. Second, I highly recommend getting a book which presents the major toolbox functions and variables within the context of their respective toolbox managers, one which describes why each manager works the way it does. My favorite is “Macintosh Revealed,” by Stephen Chernicoff. It was two volumes when I started out, but it has since grown to four. It is written in Pascal, but there are comparable books in C. David Mark’s two volume “Macintosh C Programming Primer” has gotten good grades from users online.
  90.     Next issue, in conjunction with the next article in this series, we’ll go into more detail about essentials and resources for the novice Macintosh programmer.
  91.  
  92.  
  93. Listing1:
  94. Window Prototypes
  95. void    Open_PeriodicChart     ();        
  96. void    Close_PeriodicChart     ();        
  97. void    UpDate_PeriodicChart     ();        
  98. void    Do_PeriodicChart     (EventRecord *thisEvent);    
  99.  
  100. void    Open_Elements     (int thisElement);
  101. void    Close_Elements     ();
  102. void    UpDate_Elements     ();
  103. void    Do_Elements        (EventRecord *thisEvent);
  104.  
  105. Global Window Variables
  106. DialogPtr    PeriodicChartDlog;
  107. DialogPtr    ElementsDlog;
  108.  
  109. Point        PeriodicChartWhere;
  110. Point        ElementsWhere;
  111.  
  112. Precompiler Definitions
  113. #define        ResID_PeriodicChart    201
  114. #define        ResID_Elements        202
  115.  
  116.  
  117. Listing2:
  118. void    Open_Elements    (int thisElement)
  119. {
  120.     GDHandle    saveDevice;
  121.     CGrafPtr    saveCGrafPtr;
  122.     Str255        thisString,    thatString;
  123.     
  124.  
  125.     if    (ElementsDlog!=NIL)        {SelectWindow(ElementsDlog);    return;    }
  126.     if    (thisElement<=0)            return;
  127.     if    (thisElement>Max_NumElements)    return;
  128.  
  129.     ElementsDlog = GetNewDialog    (ResID_Elements, NIL, (WindowPtr)-1);
  130.     if    (ElementsDlog==NIL)    return;
  131.     GetGWorld        (&saveCGrafPtr,&saveDevice);
  132.     SetGWorld        ((CGrafPtr)ElementsDlog,saveDevice);
  133.  
  134.     if    (thisElement>=1&&thisElement<=110)    
  135.                 GetIndString    (thisString,ChemNamesStr,thisElement);
  136.     else if        (thisElement>=111)
  137.     {
  138.         strcpy        (&thisString,"Element ");
  139.         NumToString    (thisElement,&thatString);
  140.         p2cstr        (&thatString);
  141.         strcat        (&thisString,&thatString);
  142.         c2pstr        (&thisString);
  143.     }
  144.     SetWTitle        (ElementsDlog,&thisString);
  145.     ShowWindow        (ElementsDlog);          
  146.     SelectWindow        (ElementsDlog);         
  147. }
  148.  
  149.  
  150.  
  151. Listing3:
  152. void    Close_Elements    ()
  153. {
  154.     if    (ElementsDlog==NIL)    return;
  155.     DisposDialog    (ElementsDlog);
  156.     ElementsDlog    =    NIL;
  157. }
  158.  
  159.  
  160.  
  161. Listing4:
  162. void    UpDate_PeriodicChart()
  163. {
  164.     GDHandle    saveDevice;
  165.     CGrafPtr    saveCGrafPtr;
  166.     int        loop1;
  167.     Str255    thisString;
  168.         
  169.     if    (PeriodicChartDlog==NIL)    return;
  170.     
  171.     GetGWorld    (&saveCGrafPtr,&saveDevice);
  172.     SetGWorld    ((CGrafPtr)PeriodicChartDlog,saveDevice);
  173.     
  174.     BeginUpdate    (PeriodicChartDlog);
  175.     
  176.     RGBForeColor    (&Color_LtBlue);
  177.     RGBBackColor    (&Color_White);
  178.     
  179.     for    (loop1=13;loop1<=PeriodicChartDlog->portRect.right-13;loop1+=2)
  180.     {
  181.         MoveTo    (loop1,PeriodicChartDlog->portRect.top+13);
  182.         Line    (0,0);
  183.         
  184.         MoveTo    (loop1,PeriodicChartDlog->portRect.bottom-13);
  185.         Line    (0,0);
  186.     }
  187.     for    (loop1=13;loop1<=PeriodicChartDlog->portRect.bottom-13;loop1+=2)
  188.     {
  189.         MoveTo    (PeriodicChartDlog->portRect.left+13,    loop1);
  190.         Line    (0,0);
  191.         
  192.         MoveTo    (PeriodicChartDlog->portRect.right-13,    loop1);
  193.         Line    (0,0);
  194.     }
  195.     
  196.     TextFont    (geneva);
  197.     TextSize    (9);
  198.     for    (loop1=1;loop1<=Max_NumElements;loop1++)
  199.     {
  200.         RGBForeColor    (&Color_Blue);
  201.         FrameRect    (&PeriodicRects[loop1]);
  202.         RGBForeColor    (&Color_Black);
  203.         GetIndString    (thisString,ChemSymbsStr,loop1);
  204.         MoveTo        ((PeriodicRects[loop1].left)/2+(PeriodicRects[loop1].right) /2,
  205.                 (PeriodicRects[loop1].top) /2+(PeriodicRects[loop1].bottom)/2);
  206.         Move        (-StringWidth(thisString)/2,5);
  207.         DrawString    (thisString);        
  208.     }
  209.     RGBForeColor    (&Color_Black);
  210.     TextFont    (systemFont);
  211.     TextSize    (12);
  212.  
  213.     EndUpdate    (PeriodicChartDlog);
  214.  
  215.     SetGWorld    (saveCGrafPtr,saveDevice);
  216. }
  217.  
  218.  
  219.  
  220. Listing5:
  221. void    Do_PeriodicChart    (EventRecord *thisEvent)
  222. {
  223.     long        testKey,keyCode,loop1;
  224.  
  225.     PeriodicChartWhere    = thisEvent->where;
  226.     GlobalToLocal        (&PeriodicChartWhere);
  227.  
  228.     if    (thisEvent->what==mouseDown)
  229.     {
  230.         for    (loop1=1;loop1<=Max_NumElements;loop1++)
  231.         {
  232.             if    (PtInRect(PeriodicChartWhere,&PeriodicRects[loop1])==TRUE)
  233.             {
  234.                 if    (WaitDoubleClick(&PeriodicRects[loop1])==Click_Double)    Open_Elements    (loop1);
  235.                 return;
  236.             }
  237.         }
  238.     }
  239.     else if    (    (thisEvent->what == keyDown)     &&    ((thisEvent->modifiers & cmdKey) == 0))
  240.     {
  241.         testKey         = BitAnd    (thisEvent->message,charCodeMask);
  242.         keyCode         = BitAnd    (thisEvent->message,keyCodeMask);
  243.         keyCode        /= 256;
  244.         if    (keyCode==53)
  245.         {
  246.             Close_PeriodicChart();
  247.             return;
  248.         }
  249.     }
  250. }
  251.